package com.ouc.tcp.test;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import com.ouc.tcp.client.TCP_Receiver_ADT;
import com.ouc.tcp.client.UDT_Timer;
import com.ouc.tcp.message.*;


// 添加计时器超时重传

public class TCP_Receiver extends TCP_Receiver_ADT {
    private TCP_PACKET ackPack;	    //回复的ACK报文段
    int expectseqnum = 1;                 //用于记录当前期待的包序号
    LinkedList<Integer> windows;  //接收方的包序号缓存

    /*构造函数*/
    public TCP_Receiver() {
        super();	//调用超类构造函数
        super.initTCP_Receiver(this);	//初始化TCP接收端
        windows = new LinkedList<Integer>();
    }

    @Override
    //接收到数据报：检查校验和，设置回复的ACK报文段
    public void rdt_recv(TCP_PACKET recvPack) {
        // 检查校验码，生成ACK
        if(CheckSum.computeChkSum(recvPack) == recvPack.getTcpH().getTh_sum()) {
            // 生成ACK报文段（设置确认号）
            int seq = recvPack.getTcpH().getTh_seq();
            // System.out.println("Receive seq: " + seq);
            // System.out.println("Expect seq: " + expectseqnum);
            if (seq < expectseqnum) {
                // 一个重复发的包,直接丢弃
                // System.out.println("Duplicate pkt: " + recvPack.getTcpH().getTh_seq());
                // 依旧给一个回复
                tcpH.setTh_ack(expectseqnum-1);
                ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
                tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
                reply(ackPack);
                return;
            } else {

                // 缓存收到的包
                recvBuffer.put(seq, recvPack.getTcpS().getData());

                // 选择插入排序
                int pos = 0;
                for (int e : windows) {
                    if (e > seq) {
                        break;
                    } else {
                        pos += 1;
                    }
                }
                windows.add(pos, seq);


                // 跨过连续的缓存的包,计算下一个期待的包
                int next_seq = expectseqnum;
                // System.out.println("New seq compute start");

                // 判断链表首元素是否为我们要的序列, 是就弹出, 期待包序列加一
                while (!windows.isEmpty() && windows.getFirst() == next_seq) {

                    next_seq++;
                    // 移除首元素,相当于窗口滑动
                    windows.poll();
                }

                // System.out.println("compute new seq: " + next_seq);
                // 将接收到的正确有序的数据插入data队列，准备交付
                for (int i = expectseqnum; i < next_seq; i++) {
                    dataQueue.add(recvBuffer.get(i));
                    recvBuffer.remove(i); // recvBuffer只保存当前窗口中的包
                }
                deliver_data();

                // 将下一个期望的包移动到第一个不连续的位置
                expectseqnum = next_seq;

                // 回复最大的有序的包
                tcpH.setTh_ack(expectseqnum - 1);
                ackPack = new TCP_PACKET(tcpH, tcpS, recvPack.getSourceAddr());
                tcpH.setTh_sum(CheckSum.computeChkSum(ackPack));
                reply(ackPack);
            }

        }else{
            // System.out.println("An Error pack: " + recvPack.getTcpH().getTh_seq());
        }

        System.out.println();
    }

    @Override
    //交付数据（将数据写入文件）；不需要修改
    public void deliver_data() {
        //检查dataQueue，将数据写入文件
        File fw = new File("recvData.txt");
        BufferedWriter writer;

        try {
            writer = new BufferedWriter(new FileWriter(fw, true));

            //循环检查data队列中是否有新交付数据
            while(!dataQueue.isEmpty()) {
                int[] data = dataQueue.poll();

                //将数据写入文件
                for(int i = 0; i < data.length; i++) {
                    writer.write(data[i] + "\n");
                }

                writer.flush();		//清空输出缓存
            }
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    //回复ACK报文段
    public void reply(TCP_PACKET replyPack) {
        //设置错误控制标志
        tcpH.setTh_eflag((byte) 7);    //eFlag=0，信道无错误
        //发送数据报
        client.send(replyPack);
        // System.out.println("Start reply ack " + replyPack.getTcpH().getTh_ack());
    }

}